package main

/**
词法器
用于解析配置文件
*/

import (
	"bytes"
	"encoding/json"
	"io/ioutil"
	"log"
	"os"
	"regexp"
	"strconv"
	"strings"
)

type Config struct {
	Servers        []*ServerBlock `json:"Servers"`
	Listen         uint16
	sb             *strings.Builder
	empBuf         *bytes.Buffer
	spaceReg       *regexp.Regexp
	penetrationReg *regexp.Regexp
}

type ServerBlock struct {
	Listen       uint16            `json:listen`
	Locations    []*LocationBlock  `json:"locations"`
	ProxyPass    string            `json:"proxy_pass"`
	Penetration  *PenetrationBlock `json:"penetration"`
	FirewallType string            `json:"firewall_type"`
	Firewalls    []*FirewallBlock  `json:"firewall"`
}

type LocationBlock struct {
	ProxyPass        string   `json:"proxy_pass"`
	Alias            string   `json:"alias"`
	Url              string   `json:"url"`
	Ssi              string   `json:"ssi"`
	Index            []string `json:"index"`
	LogDay           string   `json:"log_day"`
	LogFilter        []string `json:"log_filter"`
	FormatterParam   string   `json:"formatter_param"`
	TransmitFilter   []string `json:"transmit_filter"`
	Transmit         []string `json:"transmit"`
	TransmitAll      []string `json:"transmit_all"`
	TransmitInterval []uint16 `json:"transmit_interval"`
}

type FirewallBlock struct {
	Username string `json:"username"`
	//Password string `json:"password"`
	//Type     string `json:"type"`
	Database string `json:"database"`

	//ModifyUsername string `json:"modify_username"`
	//ModifyPassword string `json:"modify_password"`
	//ModifyDatabase string `json:"modify_database"`

	KeywordFilter []string `json:"keyword_filter"`
}

type PenetrationBlock struct {
	Url       string `json:"url"`
	StartPort uint16 `json:"start_port"`
	EndPort   uint16 `json:"end_port"`
	BasePort  uint16 `json:"base_port"`
}

func NewConfig() *Config {
	ret := &Config{
		sb:             new(strings.Builder),
		empBuf:         new(bytes.Buffer),
		spaceReg:       regexp.MustCompile("[\\s\\t]+"),
		penetrationReg: regexp.MustCompile("[\\s\\t]*:[\\s\\t]*|[\\s\\t]*-[\\s\\t]*|[\\s\\t]+"),
	}

	return ret
}

func (this *Config) LoadJson(jsonstr []byte) error {
	err := json.Unmarshal(jsonstr, this)
	return err
}

func (this *Config) LoadJsonFile(path string) error {
	str, err := ioutil.ReadFile(path)
	if err != nil {
		return err
	}
	return this.LoadJson(str)
}

func (this *Config) LoadConf(path string) {
	str, err := ioutil.ReadFile(path)
	if err != nil {
		log.Print(err)
	}

	//去除所有的注释
	r, _ := regexp.Compile("#[^\n]+?\n")
	str = r.ReplaceAll(str, []byte("\n"))

	//serverIdex := "0"
	//li := list.New()
	ln := len(str)
	ptr := 0
	for ptr < ln {
		token := this.readToken(str, &ptr)
		switch token {
		case "server":
			//skip {
			this.readToken(str, &ptr)
			this.Servers = append(this.Servers, this.readServerBlock(str, &ptr))

		case "listen":
			if listen, err := strconv.Atoi(this.readUntilLine(str, &ptr)); err == nil {
				this.Listen = uint16(listen)
			}

		}
	}

}

func (this *Config) readServerBlock(bs []byte, ptr *int) *ServerBlock {
	var ret ServerBlock
	//readFirstChar := false
scan:
	for *ptr < len(bs) {
		token := this.readToken(bs, ptr)
		switch token {
		case "listen":
			str := this.readUntil(bs, ptr, '\n')
			if port, err := strconv.Atoi(str); err == nil {
				ret.Listen = uint16(port)
			}

		case "proxy_pass":
			ret.ProxyPass = this.readUntil(bs, ptr, '\n')

		case "penetration":
			str := this.readUntil(bs, ptr, '\n')
			arr := this.penetrationReg.Split(str, -1)
			p := new(PenetrationBlock)
			if len(arr) == 4 {
				p.Url = arr[0]
				ret.Penetration = p
				if port, err := strconv.Atoi(arr[1]); err == nil {
					ret.Penetration.BasePort = uint16(port)
				}
				if port, err := strconv.Atoi(arr[2]); err == nil {
					ret.Penetration.StartPort = uint16(port)
				}
				if port, err := strconv.Atoi(arr[3]); err == nil {
					ret.Penetration.EndPort = uint16(port)
				}
			} else if len(arr) == 3 {
				p.Url = arr[0]
				ret.Penetration = p
				if port, err := strconv.Atoi(arr[1]); err == nil {
					ret.Penetration.StartPort = uint16(port)
				}
				if port, err := strconv.Atoi(arr[2]); err == nil {
					ret.Penetration.EndPort = uint16(port)
				}
			} else {
				log.Println("invalid penetration")
			}

		case "location":
			//skip {
			url := this.readUntil(bs, ptr, '{')
			//
			block := this.readLocationBlock(bs, ptr)
			block.Url = url
			ret.Locations = append(ret.Locations, block)

		case "firewall_type":
			ret.FirewallType = this.readUntilLine(bs, ptr)

		case "firewall":
			//skip {
			this.readUntil(bs, ptr, '{')
			//skip \n
			this.readUntil(bs, ptr, '\n')
			block := this.readFirewallBlock(bs, ptr)
			ret.Firewalls = append(ret.Firewalls, block)

		case "}":
			break scan
		}
	}

	return &ret
}

func (this *Config) readFirewallBlock(bs []byte, ptr *int) *FirewallBlock {
	var ret FirewallBlock
scan:
	for *ptr < len(bs) {
		token := this.readToken(bs, ptr)
		switch token {

		case "username":
			ret.Username = this.readUntilLine(bs, ptr)

		//case "password":
		//	ret.Password = this.readUntilLine(bs, ptr)

		case "database":
			ret.Database = this.readUntilLine(bs, ptr)

		//case "type":
		//	ret.Type = this.readUntilLine(bs, ptr)
		//case "modify_username":
		//	ret.ModifyUsername = this.readUntilLine(bs, ptr)
		//case "modify_password":
		//	ret.ModifyPassword = this.readUntilLine(bs, ptr)
		//case "modify_database":
		//	ret.ModifyDatabase = this.readUntilLine(bs, ptr)

		case "keyword_filter":
			ret.KeywordFilter = this.readArrayUntilLine(bs, ptr)

		case "}":
			break scan

		}
	}

	return &ret
}

func (this *Config) readLocationBlock(bs []byte, ptr *int) *LocationBlock {
	var ret LocationBlock
	//ret := &LocationBlock{}
	//_type := reflect.TypeOf(&ret)
	//_value := reflect.ValueOf(&ret)
	//mp := make(map[string]int)
	//fmt.Println(_type)
	//for i := 0; i < _type.Elem().NumField(); i++ {
	//	mp[_type.Elem().Field(i).Tag.Get("json")] = i
	//}
scan:
	for *ptr < len(bs) {
		token := this.readToken(bs, ptr)
		switch token {
		case "proxy_pass":
			ret.ProxyPass = this.readUntil(bs, ptr, '\n')

		case "alias":
			ret.Alias = this.readUntil(bs, ptr, '\n')

		case "ssi":
			ret.Ssi = this.readUntil(bs, ptr, '\n')

		case "index":
			token := this.readUntil(bs, ptr, '\n')
			ret.Index = this.spaceReg.Split(token, 100)

		case "log_day":
			ret.LogDay = this.readUntil(bs, ptr, '\n')
			li := strings.LastIndex(ret.LogDay, "/")
			if li != len(ret.LogDay)-1 {
				ret.LogDay += "/"
			}
			if !FileExists(ret.LogDay) {
				_ = os.MkdirAll(ret.LogDay, os.ModePerm)
			}

		case "log_filter":
			ret.LogFilter = this.readArrayUntilLine(bs, ptr)

		case "formatter_param":
			ret.FormatterParam = this.readUntil(bs, ptr, '\n')

		case "transmit":
			ret.Transmit = this.readArrayUntilLine(bs, ptr)

		case "transmit_all":
			ret.TransmitAll = this.readArrayUntilLine(bs, ptr)

		case "transmit_filter":
			ret.TransmitFilter = this.readArrayUntilLine(bs, ptr)

		case "transmit_interval":
			arr := this.readArrayUntilLine(bs, ptr)
			ret.TransmitInterval = make([]uint16, len(arr))
			for i, v := range arr {
				if iv, err := strconv.Atoi(v); err == nil {
					ret.TransmitInterval[i] = uint16(iv)
				}
			}

		case "}":
			break scan

			//default:
			//	if idex, ok := mp[token]; ok {
			//		_value.Elem().Field(idex).SetString(this.readUntil(bs,ptr,'\n'))
			//	}
		}
	}
	return &ret
}

func (this *Config) readToken(bs []byte, ptr *int) string {
	this.sb.Reset()
	for ; *ptr < len(bs); *ptr++ {
		ch := bs[*ptr]
		//如果是字符，直接返回
		if ch == '{' || ch == '}' || ch == '\n' {
			if this.sb.Len() > 0 {
				return this.sb.String()
			} else {
				*ptr++
				return string(ch)
			}
		} else {
			if IsEmptyChar(ch) {
				if this.sb.Len() > 0 {
					*ptr++
					return this.sb.String()
				}
			} else {
				this.sb.WriteByte(ch)
			}
		}
	}

	return ""
}

func (this *Config) readUntilLine(bs []byte, ptr *int) string {
	return this.readUntil(bs, ptr, '\n')
}

func (this *Config) readArrayUntilLine(bs []byte, ptr *int) []string {
	token := this.readUntilLine(bs, ptr)
	return this.spaceReg.Split(token, -1)
}

func (this *Config) readUntil(bs []byte, ptr *int, ch byte) string {
	this.sb.Reset()
	this.empBuf.Reset()
	for ; *ptr < len(bs); *ptr++ {
		b := bs[*ptr]
		if b == ch {
			*ptr++
			return this.sb.String()
		} else {
			if IsEmptyChar(b) {
				this.empBuf.WriteByte(b)
			} else {
				//达到文字的时候，如果有空行，直接追加在后面
				if this.empBuf.Len() > 0 {
					this.sb.Write(this.empBuf.Bytes())
					this.empBuf.Reset()
				}
				this.sb.WriteByte(b)
			}
		}
		//if !isEmptyChar(b) && b != ch {
		//	if !begin {
		//		begin = true
		//	}
		//	sb.WriteByte(b)
		//} else if b == ch {
		//	*ptr++
		//	return sb.String()
		//}
	}
	return ""
}
